/** 自定义的常用函数的类,属于静态类

  用上win10做了小修改,看来不同系统在按键按住时的vk码不同,这里按住时很大的正数,
  放开会根据连按情况变为0或1...
  
  By setycyas @2025-02-04
*/

class MyFuns {

  /* 公有函数
  */
  ;从热键符号中获取真实热键.因为设定热键时使用的热键符号可能带有其他字符,目前只对应了前置的~字符.
  static getHotkey(hotkeyStr){
    local result := hotkeyStr
    if (SubStr(hotkeyStr, 1, 1) == "~")
	  result := SubStr(hotkeyStr, 2)
    return result
  }

  ;创建一个array,初值为startValue,步长为step,终值在step为正数时小于等于endValue,在step为负数时大于等于endValue
  ;注意在默认step下,初值终值都能取到,这与python的range不同,是autohotkey风格的range
  static range(startValue, endValue, step := 1){
    local v := startValue, result := []
    if (step == 0){
      return result
    }
    while (((step > 0) and (v <= endValue)) or ((step < 0) and (v >= endValue))) {
      result.push(v)
	  v += step
    }
    return result
  }

  ;把字符串数组strArr连起来,分隔符为seq
  static join(strArr, seq){
    local result := strArr[1], n := strArr.Length-1
    loop n {
      result := result . seq . strArr[A_Index+1]
    }
    return result
  }

  ;寻找一个数组的最大或最小值
  static arrMax(arr){
    local result := arr[1], n := arr.Length-1
    loop n {
      if (arr[A_Index+1] > result) {
	    result := arr[A_Index+1]
	  }
    }
    return result
  }
  static arrMin(arr){
    local result := arr[1], n := arr.Length-1
    loop n {
      if (arr[A_Index+1] < result) {
	    result := arr[A_Index+1]
	  }
    }
    return result
  }

  ;合并两个数组
  static arrCombine(arr1, arr2){
    local result := arr1.Clone()
    loop arr2.Length {
      result.push(arr2[A_Index])
    }
    return result
  }

  ;获取一个随机字符串,每一个字符可能是数字或小写字符,参数n为字符串长度
  static getRandomStr(n){
    local result := []
    local randNum
    loop n {
      randNum := Random(1, 36) ;10个数字加26个小写字母,一共有36种可能
      if (randNum < 11) {
        result.push(Chr(randNum+47))
      } else {
        result.push(Chr(randNum+86))
      }
    }
    return MyFuns.join(result, "")
  }

  ;** 与按键相关的函数
  ;按键,按下键,弹起键
  static press(key, duration := 25){
    send("{" . key . " down}")
    sleep(duration)
    send("{" . key . " up}")
  }
  static keyDown(key){
    send("{" . key . " down}")
  }
  static keyUp(key){
    send("{" . key . " up}")
  }

  ;连续按下多个按键keys[],每次间隔记录在delays[],最后间隔时间结束后全部放手.keys和delays长度要相等
  static pressKeys(keys, delays){

    local i, key, delay
    ;检测长度,不相等则退出
    if (keys.Length != delays.Length){
      return
    }
    ;按下按键
    for i, key in keys {
      delay := delays[i]
      send("{" . key . " down}")
      sleep(delay)
    }
    ;放开按键
    for i, key in keys {
      send("{" . key . " up}")
    }

  }

  ;技能输入,按键组合为keys,间隔为delays,相邻的两个按键有重合时间,也就是说按下下一个键才会放开上一个键
  ;keys和delays长度必须相等且最小为2
  static inputSkill(keys, delays){
    local i, curKey, curDelay
    ;检测长度
    if ((keys.Length != delays.Length) or (keys.Length < 2)){
      return
    }
    ;连续按键
    for i, curKey in keys {
      curDelay := delays[i]
      if (i < 3){
        send("{" . curKey . " down}")
        sleep(curDelay)
      } else {
        send("{" . keys[i-2] . " up}")
        send("{" . curKey . " down}")
        sleep(curDelay)
      }
    }
    ;放开最后的2个键
    send("{" . keys[keys.Length-1] . " up}")
    send("{" . keys[keys.Length] . " up}")
  }
  
  ;为了增加效率,做出传入vk的按键按下判断函数,效果其实与getKeyState一样
  ;vk码对应的按键被按下则返回true,否则返回false
  static getVkState(vk){
    if (DllCall("GetKeyState", "int", vk) > 1) {
      return true
    } else {
      return false
    }
  }
  
  ;用DllCall自制的GetKeyState,底层效果比ahk v2的好
  ;但是!检测放开与否时,必须用~key的写法!!!否则会因为按下
  ;被屏蔽而无法检测!
  static getKeyState(key){
    local vk := GetKeyVK(MyFuns.getHotkey(key))
    return MyFuns.getVkState(vk)
  }
 
  ;等待指定的vk码的按键放开,等待waitTime毫秒,一放开就返回true,
  ;等待指定时间还没放开则返回false
  static waitVkUp(vk, waitTime, waitDuration := 50){
    local totalWait := 0 ;总等待时间
    local sleepTime := 0 ;下一次等待时间
    while (totalWait < waitTime) {
      sleepTime := (waitTime-totalWait > waitDuration) ? (waitDuration) : (waitTime-totalWait)
      if (!MyFuns.getVkState(vk)) {
        return true
      }
      sleep(sleepTime)
      totalWait += sleepTime
    }
    return false
  }
 ;等待按键key放开,等待waitTime毫秒,每waitDuration毫秒检查一次,若发现按键放开返回true,等待完毕未放开返回false
  ;发现waitDuration太小容易出问题,原来默认20,现在改为50
  static waitKeyUp(key, waitTime, waitDuration := 50){
    local vk := GetKeyVK(MyFuns.getHotkey(key))
    return MyFuns.waitVkUp(vk, waitTime, waitDuration)
  }
  
  ;检查4个方向键,如按住则先松开
  static clearDirections(){
    static keys := ["left", "up", "right", "down"]
    static vkeys := [37, 38, 39, 40] ;方向键与方向键对应的虚拟键盘码
    loop 4 {
      if (DllCall("GetKeyState", "int", vkeys[A_Index]) < 0) {
        MyFuns.keyUp(keys[A_Index])
      }
    }
  }
  
  ;设定窗口位置和大小的代码,关键是WinMove,包括了移动和调整大小
  static setWindow(wTitle, width, height, x := 230, y:= 40){
    local uniqueID := WinExist(wTitle)
    if (uniqueID) {
      WinMove(x, y, width, height, wTitle)
    } else {
      Msgbox(wTitle . " not found!")
    }
  }
  
  ;简单的连续按下多个按键
  static simpleInput(keys, duration := 30, delay := 30){
    local i, v
    ;连续按键
    for i, v in keys {
      send("{" . v . " down}")
      sleep(duration)
      send("{" . v . " up}")
      sleep(delay)
    }
  }
  
}

/* 测试
*/

if (A_ScriptName == "MyFuns.2.ahk"){

  ;测试getHotkey(hotkeyStr)
  ;MsgBox(MyFuns.getHotkey("~space"))
  ;MsgBox(MyFuns.getHotkey("delete"))
  ;测试range,join,arrMax,arrMin
  ;arr1 := MyFuns.range(1, 10)
  ;Msgbox(MyFuns.join(arr1, ","))
  ;MsgBox(MyFuns.arrMax(arr1))
  ;MsgBox(MyFuns.arrMin(arr1))
  ;测试getRandomStr
  ;MsgBox(MyFuns.getRandomStr(10))
  ;怒首领蜂大复活1.5选strong模式密码,测试simpleInputB C B A D ↓ ↑ A A D D C C B B ← ← A → ← ← ←
  msgbox("start!")
  keys := ["space", "s", "space", "a", "d", "down", "up", "a", "a", "d", "d", "s", "s", "space", "space",
    "left", "left", "a", "right", "left", "left", "left"]
  sleep(5000)
  MyFuns.simpleInput(keys)
  sleep(1000)
  msgbox("OK!")
}
